package com.springcachetest.Configure;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;

import java.util.concurrent.Callable;

/**
 * Spring Redis Cache
 * - 容忍Redis连接异常
 * - 容忍其他异常（如序列化）
 * - 当查询缓存操作遇到任何异常时，当作"缓存未命中"处理（返回null）
 * - 当设置或清除缓存操作遇到任何异常时，当作"操作成功"处理
 *
 * @author 易永健[of1972]
 *         company qianmi.com
 *         Date    2016-12-21
 */
@Slf4j
public class RedisFallbackCacheManager extends RedisCacheManager {
    public RedisFallbackCacheManager(RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    public Cache getCache(String name) {
        return new RedisCacheWrapper(super.getCache(name));
    }

    protected static class RedisCacheWrapper implements Cache {

        private final Cache delegate;

        RedisCacheWrapper(Cache redisCache) {
            this.delegate = redisCache;
        }

        @Override
        public String getName() {
            return delegate.getName();
        }

        @Override
        public Object getNativeCache() {
            return delegate.getNativeCache();
        }

        @Override
        public Cache.ValueWrapper get(Object key) {
            try {
                return delegate.get(key);
            } catch (Exception e) {
                return handleErrors("get", key, e);
            }
        }

        @Override
        public <T> T get(Object key, Class<T> type) {
            try {
                return delegate.get(key, type);
            } catch (Exception e) {
                return handleErrors("get", key, e);
            }
        }

        @Override
        public <T> T get(Object key, Callable<T> valueLoader) {
            return delegate.get(key, valueLoader);
        }

        @Override
        public void put(Object key, Object value) {
            try {
                delegate.put(key, value);
            } catch (Exception e) {
                handleErrors("put", key, e);
            }
        }

        @Override
        public ValueWrapper putIfAbsent(Object key, Object value) {
            try {
                return delegate.putIfAbsent(key, value);
            } catch (Exception e) {
                return handleErrors("putIfAbsent", key, e);
            }
        }

        @Override
        public void evict(Object key) {
            try {
                delegate.evict(key);
            } catch (Exception e) {
                handleErrors("evict", key, e);
            }
        }

        @Override
        public void clear() {
            try {
                delegate.clear();
            } catch (Exception e) {
                handleErrors("clear", "All", e);
            }
        }

        <T> T handleErrors(String operation, Object key, Exception e) {
            if (e instanceof RedisConnectionFailureException || e instanceof RedisSystemException) {
                log.warn("[Cache-Fail][Redis][{}]key: {}: {}", operation, key, e.getMessage());
            } else {
                log.warn("[Cache-Fail][Other][{}]key: {}", operation, key, e);
            }
            // fallback: cache missing
            return null;
        }
    }

}
